﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Controls.Primitives;
using nl.tno.ict.multitouch.wpfmultitouch.controls;
using TouchLibEvents;
using System.Timers;
using nl.tno.ict.multitouch.wpfmultitouch.events;
using System.Collections;
using System.Windows.Media;

namespace nl.tno.ict.multitouch.wpfmultitouch
{

    public class TouchManagerGrid: ITouchManager
    {
        private Hashtable _fingerCurrentPos;
        private Hashtable _fingerStartPos;

        private FrameworkElement _element;
        private FrameworkElement _parent;

        private int _zIndexOld;

        private AdornerLayer _adornerLayer;
        private SimpleCircleAdorner _simpleCircleAdorner;
        private ImageAdorner _imageAdorner;

        private Hashtable _timerTable = new Hashtable();

        private bool _simulationEnabled = false;

        public event Events.TouchEventHandler NewTouch;
        public event Events.TouchEventHandler UpdateTouch;
        public event Events.TouchEventHandler RemoveTouch;
        
        #region Properties

        /// <summary>
        /// Gets the amount of fingers touching the object
        /// </summary>
        public int FingerCount { get { return this._fingerCurrentPos.Count; } }

        public bool CanBeClicked
        {
            get { return (bool)_element.GetValue(MTProperties.CanBeClickedProperty); }
            set { _element.SetValue(MTProperties.CanBeClickedProperty, value); }
        }

        public bool CanBeDragged
        {
            get { return (bool)_element.GetValue(MTProperties.CanBeDraggedProperty); }
            set { _element.SetValue(MTProperties.CanBeDraggedProperty, value); }
        }

        public bool CanBeScaled
        {
            get { return (bool)_element.GetValue(MTProperties.CanBeScaledProperty); }
            set { _element.SetValue(MTProperties.CanBeScaledProperty, value); }
        }

        public bool CanBeRotated
        {
            get { return (bool)_element.GetValue(MTProperties.CanBeRotatedProperty); }
            set { _element.SetValue(MTProperties.CanBeClickedProperty, value); }
        }

         public bool IsScaling
        {
            get { return (bool)_element.GetValue(MTProperties.IsScalingProperty); }
            set { _element.SetValue(MTProperties.IsScalingProperty, value); }
        }

        public bool IsDragging
        {
            get { return (bool)_element.GetValue(MTProperties.IsDraggingProperty); }
            set { _element.SetValue(MTProperties.IsDraggingProperty, value); }
        }

        public bool IsRotating
        {
            get { return (bool)_element.GetValue(MTProperties.IsDraggingProperty); }
            set { _element.SetValue(MTProperties.IsDraggingProperty, value); }
        }

        public bool AdornersEnabled
        {
            get { return (bool)_element.GetValue(MTProperties.AdornersEnabledProperty); }
            set { _element.SetValue(MTProperties.AdornersEnabledProperty, value); }
        }

        /// <summary>
        /// Gets or sets the onTop property. The z-index changes when a finger hits an object.
        /// </summary>
        public bool OnTop
        {
            get { return (bool)_element.GetValue(MTProperties.OnTopProperty); }
            set { _element.SetValue(MTProperties.OnTopProperty, value); }
        }

        #endregion

        public TouchManagerGrid(FrameworkElement element, FrameworkElement parent, bool simulationEnabled)//Verandert in FrameworkElement, er kunnen een aantal methoden verdwijnen!
        {
            _element = element;
            _parent = parent;
            _fingerCurrentPos = new Hashtable();
            _fingerStartPos = new Hashtable();

            _zIndexOld = MTCanvas.GetZIndex(_element);

            this._simulationEnabled = simulationEnabled;
            
            // Creates the adorners of an element if the DependencyProperty AdornersEnabled is set to true.
            if (_element != _parent && AdornersEnabled)
            {
                _adornerLayer = AdornerLayer.GetAdornerLayer(this._element);
                _simpleCircleAdorner = new SimpleCircleAdorner(this._element);
                _imageAdorner = new ImageAdorner(this._element);
            }
        }

        public void NewFinger(Touch obj)
        {
            _element.Focus();
            if (!_fingerCurrentPos.Contains(obj.Finger.Id))
            {
                _fingerStartPos.Add(obj.Finger.Id, obj.Finger);//Add startposition
                _fingerCurrentPos.Add(obj.Finger.Id, obj.Finger);//Add startposition, this will change.
            }
           
            // Will raise a MouseDownEvent for any UIElement.
            MouseButtonEventArgs mbe = new MouseButtonEventArgs(Mouse.PrimaryDevice, 1, MouseButton.Left);
            mbe.RoutedEvent = UIElement.MouseDownEvent;
            ((UIElement)_element).RaiseEvent(mbe);

            // Will raise a MouseEnterEvenet for any UIElement.
            MouseEventArgs mea = new MouseEventArgs(Mouse.PrimaryDevice, 1, Stylus.CurrentStylusDevice);
            mea.RoutedEvent = UIElement.MouseEnterEvent;
            ((UIElement)_element).RaiseEvent(mea);

            if (_element is MTButton)
            {
                //mea = new MouseEventArgs(Mouse.PrimaryDevice, 1);
                //mea.RoutedEvent = Button.ClickEvent;
                //((UIElement)_element).RaiseEvent(mea);

                KeyEventArgs kea = new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromVisual(_element), 1, Key.None);
                kea.RoutedEvent = UIElement.KeyDownEvent;

                ((UIElement)_element).RaiseEvent(kea);
            }

            Timer t = new Timer();
            _timerTable.Add(t, obj.Finger.Id);
            t.Interval = getTimeout();
            t.Elapsed += new ElapsedEventHandler(t_Elapsed);
            t.Start();
            //Einde not moved voorbereiding

            if (_element != _parent && OnTop)
                MTCanvas.SetZIndex(_element, 20);

            if (AdornersEnabled)
                this.addAdornerLayers(1);

            //Set fingerCount var
            if (_element is MTObject)
                ((MTObject)_element).FingerCount = _fingerCurrentPos.Count;
        }

        private void t_Elapsed(object sender, ElapsedEventArgs e)
        {
            FingerObject currentpos = null;
            FingerObject startpos = null;

            object ID = _timerTable[(Timer)sender];

            currentpos = (FingerObject)_fingerCurrentPos[ID];
            startpos = (FingerObject)_fingerStartPos[ID];

            if (_fingerStartPos.Count > 0 && _fingerCurrentPos.Count > 0 &&
                currentpos != null && startpos != null &&
                Math.Abs(currentpos.CurrentPosition.X - startpos.CurrentPosition.X) < 5 &&
                Math.Abs(currentpos.CurrentPosition.Y - startpos.CurrentPosition.Y) < 5)
            {
                if (_element is MTObject)
                    ((MTObject)_element).NotMoved();
            }
            ((Timer)sender).Stop();//Stop timer
            _timerTable.Remove((Timer)sender);//Remove the timer
        }

        private double getTimeout()
        {
            if (_element is MTObject)
                return ((MTObject)_element).Timeout;
            return 1000.0;
        }
        /// <summary>
        /// When a finger leaves the object this method will be used
        /// </summary>
        /// <param name="fo"></param>
        public void RemoveFinger(Touch t)
        {
            FingerObject fo = t.Finger;
            //Remove finger from list
            _fingerStartPos.Remove((object)fo.Id);
            _fingerCurrentPos.Remove((object)fo.Id);
            
            if (AdornersEnabled)
                this.removeAdornerLayers(0);

            if (_fingerCurrentPos.Count == 0)
            {
                if(OnTop)
                    MTCanvas.SetZIndex(_element, _zIndexOld);

                MouseEventArgs mea = new MouseEventArgs(Mouse.PrimaryDevice, 1);
                mea.RoutedEvent = UIElement.MouseLeaveEvent;
                ((UIElement)_element).RaiseEvent(mea);

                if (_element is MTButton)
                {
                    KeyEventArgs kea = new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromVisual(_element), 1, Key.None);
                    kea.RoutedEvent = UIElement.KeyUpEvent;
                    ((UIElement)_element).RaiseEvent(kea);
                }

                IsDragging = false;
            }
            if (CanBeClicked &&(_element is MTRadioButton || _element is MTCheckBox))
            {
                MouseButtonEventArgs mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 1, MouseButton.Left);
                mbea.RoutedEvent = Button.ClickEvent;
                _element.RaiseEvent(mbea);
            }

            //Set fingerCount var
            if (_element is MTObject)
                ((MTObject)_element).FingerCount = _fingerCurrentPos.Count;
        }

        /// <summary>
        /// When a finger raises this method will be used
        /// </summary>
        /// <param name="fo"></param>
        public void FingerRaised(FingerObject FingerLastPos)
        {
            //Remove finger from list
            _fingerStartPos.Remove((object)FingerLastPos.Id);
            _fingerCurrentPos.Remove((object)FingerLastPos.Id);

            //Raise some event(s)
            if (_fingerCurrentPos.Count == 0)
            {
                KeyEventArgs kea = new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromVisual(_element), 1, Key.None);
                kea.RoutedEvent = UIElement.KeyUpEvent;
                ((UIElement)_element).RaiseEvent(kea);

                MouseEventArgs mea = new MouseEventArgs(Mouse.PrimaryDevice, 1, Stylus.CurrentStylusDevice);
                mea.RoutedEvent = UIElement.MouseLeaveEvent;
                ((UIElement)_element).RaiseEvent(mea);

                MouseButtonEventArgs mbe = new MouseButtonEventArgs(Mouse.PrimaryDevice, 1, MouseButton.Left);
                //mbe.RoutedEvent = UIElement.PreviewMouseUpEvent; --> Generates error:(
                //((UIElement)_element).RaiseEvent(mbe);

                mbe.RoutedEvent = UIElement.MouseUpEvent;
                ((UIElement)_element).RaiseEvent(mbe);

                //mbe.RoutedEvent = MTCheckBox.CheckedEvent;
                //_element.RaiseEvent(mbe);

                if (CanBeClicked && !this._simulationEnabled)
                {
                    MouseButtonEventArgs mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 1, MouseButton.Left);
                    mbea.RoutedEvent = Button.ClickEvent;
                    _element.RaiseEvent(mbea);
                }
            }
            //Set the z index to normal
            if (_fingerCurrentPos.Count == 0 && OnTop)
            {
                MTCanvas.SetZIndex(_element, _zIndexOld);
            }

            if (AdornersEnabled)
                this.removeAdornerLayers(0);

            //Set fingerCount var
            if (_element is MTObject)
                ((MTObject)_element).FingerCount = _fingerCurrentPos.Count;
        }
        /// <summary>
        /// Set new position of a existing finger and do some scaling, rotating and draggin 
        /// </summary>
        /// <param name="FingerNewPos">The new position of a finger</param>
        public void UpdateFinger(Touch t, List<TuioMoveFingerEventArgs> history)
        {
            FingerObject FingerNewPos = t.Finger;
            //Set the new position of the finger in _currentfingerpos
            if (fingerPosChanged((FingerObject)_fingerCurrentPos[(object)FingerNewPos.Id], FingerNewPos))
            {
                _fingerCurrentPos[(object)FingerNewPos.Id] = FingerNewPos;
            }
            else
                return;

            //Thrown element specific events
            MouseEventArgs mbe = new MouseEventArgs(Mouse.PrimaryDevice, 1, Stylus.CurrentStylusDevice);
            mbe.RoutedEvent = UIElement.MouseMoveEvent;
            ((UIElement)_element).RaiseEvent(mbe);
            
            // Will move any object within a MTGrid when it is allowed to drag.
            if (CanBeDragged)
            {
                double angle = calculateRotate(_element);
                Console.WriteLine("Angle " + angle);
                if (angle >= 0 && angle < 90)
                {
                    if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x + 30.0) - (angle * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + (angle * 0.33)))
                    {
                        if (((MTGrid)(_parent)).ColumnDefinitions.Count > MTGrid.GetColumn(_element) + 1)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) + 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x - 30.0) + (angle * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - (angle * 0.33))
                    {
                        if (MTGrid.GetColumn(_element) - 1 >= 0)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) - 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) - (angle * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + 30.0) - (angle * 0.33))
                    {
                        if (((MTGrid)(_parent)).RowDefinitions.Count > MTGrid.GetRow(_element) + 1)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) + 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) + (angle * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - 30.0 + (angle * 0.33))
                    {
                        if (MTGrid.GetRow(_element) - 1 >= 0)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) - 1);
                    }
                }
                else if (angle >= 90 && angle < 180)
                {
                    if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x + 30.0) - ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + ((angle % 90) * 0.33)))
                    {
                        if (MTGrid.GetRow(_element) - 1 >= 0)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) - 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x - 30.0) + ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - ((angle % 90) * 0.33))
                    {
                        if (((MTGrid)(_parent)).RowDefinitions.Count > MTGrid.GetRow(_element) + 1)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) + 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) - ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + 30.0) - ((angle % 90) * 0.33))
                    {
                        if (((MTGrid)(_parent)).ColumnDefinitions.Count > MTGrid.GetColumn(_element) + 1)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) + 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) + ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - 30.0 + ((angle % 90) * 0.33))
                    {
                        if (MTGrid.GetColumn(_element) - 1 >= 0)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) - 1);
                    }
                }
                else if (angle >= 180 && angle < 270)
                {
                    if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x + 30.0) - ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + ((angle % 90) * 0.33)))
                    {
                        if (MTGrid.GetColumn(_element) - 1 >= 0)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) - 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x - 30.0) + ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - ((angle % 90) * 0.33))
                    {
                        if (((MTGrid)(_parent)).ColumnDefinitions.Count > MTGrid.GetColumn(_element) + 1)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) + 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) - ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + 30.0) - ((angle % 90) * 0.33))
                    {
                        if (MTGrid.GetRow(_element) - 1 >= 0)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) - 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) + ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - 30.0 + ((angle % 90) * 0.33))
                    {
                        if (((MTGrid)(_parent)).RowDefinitions.Count > MTGrid.GetRow(_element) + 1)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) + 1);
                    }
                }
                else if (angle >= 270 && angle < 360)
                {
                    if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x + 30.0) - ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + ((angle % 90) * 0.33)))
                    {
                        if (((MTGrid)(_parent)).RowDefinitions.Count > MTGrid.GetRow(_element) + 1)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) + 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x - 30.0) + ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - ((angle % 90) * 0.33))
                    {
                        if (MTGrid.GetRow(_element) - 1 >= 0)
                            MTGrid.SetRow(_element, MTGrid.GetRow(_element) - 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X <= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) - ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).y + 30.0) - ((angle % 90) * 0.33))
                    {
                        if (MTGrid.GetColumn(_element) - 1 >= 0)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) - 1);
                    }
                    else if (FingerNewPos.CurrentPosition.X >= (((FingerObject)_fingerStartPos[FingerNewPos.Id]).x) + ((angle % 90) * 0.33) && FingerNewPos.CurrentPosition.Y <= ((FingerObject)_fingerStartPos[FingerNewPos.Id]).y - 30.0 + ((angle % 90) * 0.33))
                    {
                        if (((MTGrid)(_parent)).ColumnDefinitions.Count > MTGrid.GetColumn(_element) + 1)
                            MTGrid.SetColumn(_element, MTGrid.GetColumn(_element) + 1);
                    }
                }
            }
        }

        private double calculateRotate(DependencyObject element)
        {
            double _angle = 0.0;
            while (true)
            {
                element = VisualTreeHelper.GetParent(element);
                if (element != null && element is FrameworkElement)
                {
                    TransformGroup transformGroup = ((FrameworkElement)element).LayoutTransform as TransformGroup;
                    if (transformGroup == null)
                    {
                        transformGroup = ((FrameworkElement)element).RenderTransform as TransformGroup;
                    }
                    if (transformGroup != null && transformGroup.Children.Count > 0)
                    {
                        foreach (Transform trans in transformGroup.Children)
                        {
                            if (trans is RotateTransform)
                            {
                                _angle += ((RotateTransform)trans).Angle;
                            }
                        }
                    }
                }
                if (element == null)
                    break;
            }
            if (_angle < 0)
                return 360 + (_angle % 360);
            return _angle = (_angle % 360);
        }

        /// <summary>
        /// This method will add adorners to the specific element.
        /// </summary>
        /// <param name="fingerCount">Count of fingers</param>
        private void addAdornerLayers(int fingerCount)
         {
             if (_element != _parent)
             {
                 if (_fingerStartPos.Count == fingerCount)
                 {
                     _adornerLayer.Add(_simpleCircleAdorner);
                     _adornerLayer.Add(_imageAdorner);
                 }
             }
         }
         
        /// <summary>
        /// This method will remove adorners from the specific element.
        /// </summary>
        /// <param name="fingerCount">Count of fingers</param>
        private void removeAdornerLayers(int fingerCount)
        {
            if (_element != _parent)
            {
                if (_adornerLayer.GetAdorners(_element).Length > 0)
                {
                    if (_fingerStartPos.Count == fingerCount)
                    {
                        _adornerLayer.Remove(_simpleCircleAdorner);
                        _adornerLayer.Remove(_imageAdorner);
                    }
                }
            }
        }

        /// <summary>
        /// Check if a position of a finger is changed
        /// </summary>
        /// <param name="FingerOldPos">Old finger position</param>
        /// <param name="FingerNewPos">New finger position</param>
        /// <returns></returns>
        private bool fingerPosChanged(FingerObject FingerOldPos, FingerObject FingerNewPos)
        {
            if ((FingerNewPos.x > FingerOldPos.x + 1 || FingerNewPos.x < FingerOldPos.x - 1) ||
                FingerNewPos.y > FingerOldPos.y + 1 || FingerNewPos.y < FingerOldPos.y - 1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
                
        /// <summary>
        /// Objects can not leafe the parent object(Canvas/Grid)
        /// </summary>
        /// <param name="newHorizontalOffset"></param>
        /// <param name="newVerticalOffset"></param>
        /// <returns></returns>
        private bool clipToBounds(double newHorizontalOffset, double newVerticalOffset)
        {
            if (_element.ClipToBounds)//When cliptobounds is true go into the if statement, when not, return always true
            {
                if (newHorizontalOffset + Width(_element) <= _parent.ActualWidth && newVerticalOffset + Height(_element) <= _parent.ActualHeight &&
                    newHorizontalOffset > 0 && newVerticalOffset > 0)
                    return true;
                else
                    return false;
            }
            else
                return true;
        }


        private double Width(UIElement element)
        {
            return ((FrameworkElement)element).ActualWidth;
        }

        private double Height(UIElement element)
        {
            return ((FrameworkElement)element).ActualHeight;
        }

        #region ITouchManager Members


        public event Events.TouchEventHandler TwinClick;

        public event Events.TouchEventHandler DoubleClick;

        #endregion
    }
}
